#include "dicom/recovery/recovery.h"
#include "dicom/upload/cloud_login.h"
#include "dicom/dmv_db.h"

#include <surbasic/surbasic.h>
#include <gwbase/base/logging.h>
#include <gwbase/byte_array.h>

namespace dicom {

const int kLimit = 1000;
const SDBasic::time::Timestamp kStart = SDBasic::time::Timestamp::FromTimeT(1);

static int SurbasicLogCallback(SDLogLevel level, const char* fmt, ...) {
  if (static_cast<int>(level) < static_cast<int>(GetLogLevel())) {
    return 0;
  }
  std::string result;
  va_list ap;
  va_start(ap, fmt);
  StringPrintfV(&result, fmt, ap);
  va_end(ap);

  if (result.size() == 0 || result[result.size() - 1] != '\n')
    result.append("\n");

  const char* kPrefix = "SURCLOUD: ";
  const size_t kPrefixLen = strlen(kPrefix);
  GetCurrentSink()->WriteLog(kPrefix, kPrefixLen);
  GetCurrentSink()->WriteLog(result.c_str(), result.size());
  GetCurrentSink()->Flush();
  return 0;
}

static bool SyncFolderItem(DmvDBPtr& db, const std::string& user,
                           const TreeEntity& t) {
  DirObject d;
  if (db->GetDirByCloudId(t.id, &d) == false) {
    DirObject parent;
    if (db->GetDirByCloudId(t.parentid, &parent) == false) {
      LOG_INFO << "parent not found: " << t.parentid;
      return false;
    }
    d.parentId = parent.local_id;
    d.local_id = t.id;
    d.cloud_id = t.id;
    d.name = t.name;
    d.is_delete = 0;
    d.creator = user;
    d.createTime = d.modifiedTime = Timestamp::Now();
    db->UpdateDir(d);
  }
  return true;
}

static bool ProcessPendingDir(DmvDBPtr& db, const std::string& user,
                              DmvChangeList& changes, int iteration) {
  if (changes.empty()) return true;

  if (iteration > 15) return false;

  DmvChangeList pending;
  for (size_t i = 0; i < changes.size(); ++i) {
    SDBasic::TreeEntity& t = changes[i];
    if (t.del > 0) {
      continue;
    }
    printf("Recovering folder: %s - %s - %s\n", changes[i].id.c_str(),
           changes[i].parentid.c_str(), changes[i].name.c_str());

    if (SyncFolderItem(db, user, t) == false) {
      pending.push_back(t);
      continue;
    }
  }

  changes.clear();
  if (!pending.empty()) {
    ProcessPendingDir(db, user, pending, iteration + 1);
  }

  return true;
}

static bool RecoverFolder(DmvDBPtr& db, uint64_t key, const std::string& user) {
  DmvChangeList pending;

  int offset = 0;
  SDBasic::json::DmvChangeList changes;
  for (;;) {
    changes.clear();
    bool ok = SDBasic::GetFolderChangeLists(key, kStart, offset, kLimit, false,
                                            &changes);

    if (!ok) {
      return false;
    }

    offset += static_cast<int>(changes.size());

    // process folder
    for (size_t i = 0; i < changes.size(); ++i) {
      printf("Recovering folder: %s - %s - %s\n", changes[i].id.c_str(),
             changes[i].parentid.c_str(), changes[i].name.c_str());

      SDBasic::TreeEntity& t = changes[i];
      if (SyncFolderItem(db, user, t) == false) {
        pending.push_back(t);
        continue;
      }
    }

    if (changes.size() < static_cast<size_t>(kLimit)) break;
  }

  // ProcessPendingDir(db, user, pending, 1);
  return true;
}

static bool RecoverFile(DmvDBPtr& db, uint64_t key, const std::string& user) {
  int offset = 0;
  SDBasic::json::DmvChangeList changes;
  for (;;) {
    changes.clear();
    bool ok = SDBasic::GetFileChangeLists(key, kStart, offset, kLimit, false,
                                          &changes);

    if (!ok) {
      return false;
    }

    offset += static_cast<int>(changes.size());

    // process file
    for (size_t i = 0; i < changes.size(); ++i) {
      SDBasic::TreeEntity& t = changes[i];
      if (t.del > 0) {
        continue;
      }
      printf("Recovering file: %s - %s - %s\n", changes[i].id.c_str(),
             changes[i].parentid.c_str(), changes[i].name.c_str());

      FileObject fo;
      if (db->GetFileByCloudID(t.id, &fo) == false) {
        DirObject parent;
        if (db->GetDirByCloudId(t.parentid, &parent) == true) {
          fo.parentId = parent.local_id;
        } else {
          LOG_ERROR << "file without parent: " << t.id << " - " << t.name;
          continue;
        }

        fo.local_id = GenFileId();
        fo.name = t.name;
        fo.cloud_id = t.id;
        fo.createTime = Timestamp::Now();
        fo.modifiedTime = Timestamp::Now();
        fo.creator = user;
        fo.size = t.filesize;
        fo.digest = t.md5digest;
        std::string hex_key = ByteArray::genRandom(32).toHexStr();
        fo.hex_key = "";
        assert(!fo.parentId.empty());
        db->UpdateFile(fo);
      }
    }

    if (changes.size() < static_cast<size_t>(kLimit)) break;
  }

  return true;
}

bool DoRecovery(const DicomConf* conf, const std::string& user,
                const std::string& password) {
  // init surbasic
  SDBasic::log_callback = SurbasicLogCallback;
  SDBasic::Init();

  // do login
  SDBasic::AccessToken surdoc_token;
  int64_t key;
  std::string root_dir_id;
  std::string server = conf->GetCloudServer();
  Status s =
      LoginToCloud(server, user, password, &surdoc_token, &key, &root_dir_id);
  if (!s.ok()) {
    return false;
  }

  DmvDBManager* mgr = NewDmvDBManager(conf);
  assert(mgr != NULL);

  DmvDBPtr dmv = mgr->GetDB(user);
  dmv->InsertRootDirNode("0", root_dir_id);

  if (RecoverFolder(dmv, key, user) == false) return false;

  if (RecoverFile(dmv, key, user) == false) return false;

  return true;
}
}
